Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Volshell: Add Dedicated Method to retrieve EPROCESS/Task object given the PID #1381

Open
wants to merge 3 commits into
base: develop
Choose a base branch
from

Conversation

the-rectifier
Copy link

This PR introduces a gp() (get_process()/get_task()) method to the Windows and Linux Volshell respectively.

I couldn't find a way for one to (quickly) grab an arbitrary EPROCESS/Task object from the shell and I thought it'd be quicker, when interactively exploring structures, to have a dedicated method rather than repeat a one-liner each time.

Also, update linux.pslist version requirements, since they changed in fa06064:

Unsatisfied requirement plugins.Volshell.pslist: Version 2.0.0 dependency on volatility3.plugins.linux.pslist.PsList unmet
Unable to validate the plugin requirements: ['plugins.Volshell.pslist']

@atcuno
Copy link
Contributor

atcuno commented Dec 5, 2024

Hello @the-rectifier - nice timing on this as I was recently working on a similar feature as forcing people to do the list comprehension is pretty miserable as you noted.

With that said, accepting only a pid is not robust enough for this feature to be fully usable. There are a few reasons for this:

  1. Smear can make where multiple processes (one active and one or more terminated) have the same PID, making it non-deterministic.

  2. When specifying the pid, the only thing that can be done to find the process is walk the list (as your code does). This makes it impossible to specify processes hidden from the list by a rootkit and/or missing from the list due to smear.

To work around this, the offset (virtual or physical) needs to be allowed to be specified as this is 1) 100% precise on which process to analyze 2) supports analysis of processes found from psscan / psxview. The cleanest way in my view would be optional parameters like pid=None,v_offset=None,p_offset=None and then the code branch based on which is set.

If you are willing to work these offset parameters into your code then I can drop my code as I would rather encourage more external developers to contribute to the framework.

Copy link
Member

@ikelos ikelos left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generally looks fine, thanks! Just wondering about an alias to keep the naming more consistent...

@@ -50,6 +58,7 @@ def construct_locals(self) -> List[Tuple[List[str], Any]]:
result += [
(["ct", "change_task", "cp"], self.change_task),
(["lt", "list_tasks", "ps"], self.list_tasks),
(["gp", "get_task"], self.get_task),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How do we feel about the naming? Should it be gt in linux, or will the difference be confusing? Should we call it get_process instead to keep with windows? We can allow alternatives (see list_tasks as both lt and ps in the line above). Perhaps we should have it as gt and then with gp as a secondary?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

gt is reserved for generate_tree afaik

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, you're right... shame... 5:S Would it be worth aliasing it to get_process as well, or do we just live with the inconsistency?

@@ -40,6 +40,14 @@
return None
print(f"No task with task ID {pid} found")

def get_task(self, pid):

Check notice

Code scanning / CodeQL

Explicit returns mixed with implicit (fall through) returns Note

Mixing implicit and explicit returns may indicate an error as implicit returns always return None.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please make sure this ends in an explicit return None if the pid isn't found or there's no tasks.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It also looks like there's two bits of whitespace that are causing black to complain

@the-rectifier
Copy link
Author

Hello @atcuno, thanks for the reply!

I wasn't aware of the issues with smearing. I just hacked up a quick way to interact with structures now that I started working on my thesis.

That being said, I am not that familiar with volatility internals yet, to promise a timeline. I might look into a proper version sometime in the future but I think it'd be better to continue with your codebase

@atcuno
Copy link
Contributor

atcuno commented Dec 5, 2024

@the-rectifier don't give up yet. The basic idea would be to allow either the virtual address or physical offset to be specified and then create an _EPROCESS object at it.

For physical offsets, there is a helper function:

def virtual_process_from_physical(

Let me know if you have any questions.

@ikelos
Copy link
Member

ikelos commented Dec 5, 2024

I'd also be wary of putting too much into the generic volshell plugin. If a plugin can do it better I think we should leave the plugin to do it (even if that includes working on a physical process offset). Linking volshell functionality against a specific plugin (even a stock one) is a bad idea, as would duplicating the functionality be. I'm sure there's a solution, but I don't think it's necessary for the immediate term...

@atcuno
Copy link
Contributor

atcuno commented Dec 5, 2024

I would be fine with code duplication in this case, even if copy/paste from the plugin, as being limited to pid= is very limiting and makes it impossible to use volshell on processes hidden by rootkits.

@ikelos
Copy link
Member

ikelos commented Dec 5, 2024

... and makes it impossible to use volshell on processes hidden by rootkits.

(layer_name) >>> import volatility3.plugins.linux.pslist
(layer_name) >>> dpo(volatility3.plugins.linux.pslist.PsList, kernel=self.kernel)

I think you mean inconvenient. I've no problem loading up specific kernel offsets physical or virtual, but making volshell depend on psscan to find them rather than having to put a couple extra lines isn't worth the complexity it would bring to the command line tool in my opinion.

It's really not difficult to make use of a plugin and if that's too many lines to type, a snippet would reduce that. We're currently in a state where the dependency on simple pslist was missed when the main pslist plugin got version bumped, meaning that stock volshell on the current commit doesn't load because of that dependency. I'm not keen to add more when the plugin output is so readily accessible...

@atcuno
Copy link
Contributor

atcuno commented Dec 5, 2024

The psscan link was to the virtual_process_from_physical helper function.

I was saying that code can be copy/pasted into the volshell side to avoid the dependency. Then passing in a physical offset to get the _EPROCESS would work inside of volshell. That offset would come from the user running psscan or psxview first and noticing the hidden process.

@ikelos
Copy link
Member

ikelos commented Dec 5, 2024

My bad, that function doesn't look overly complex so I think I'd be ok duplicating it too...

@the-rectifier
Copy link
Author

Thanks for the replies, all. I played with the code a bit, and I added this to the windows side of things:

kvo = self.config['kernel.offset']
ntkrnlmp = self.context.module(self.current_symbol_table, layer_name='layer_name', offset=kvo)

eproc = ntkrnlmp.object(
    object_type='_EPROCESS',
    offset=v_offset,
    absolute=True
)

As @atcuno suggested, much like the psscan plugin, since we have the virtual address we can just grab an _EPROCESS object at that address.

However, I still can't wrap my head around what to do when a physical address is provided. Is there a way to get from the physical address to the corresponding virtual one?

@eve-mem
Copy link
Contributor

eve-mem commented Dec 7, 2024

@the-rectifier if you look at the linux.psscan as an example you'll see that the object is made with offsets on the physical layer but its native layer is set to the virtual one.

It's a sort of magical 'it just works'TM magic part of vol. It allows you to make an object on one layer, but then follow any pointers etc in another.

https://github.com/volatilityfoundation/volatility3/blob/develop/volatility3%2Fframework%2Fplugins%2Flinux%2Fpsscan.py#L148-L154

@eve-mem
Copy link
Contributor

eve-mem commented Dec 7, 2024

Also, this discussion has made me think it might be useful to share scripts that people could import by rs that makes some customisation for specific tasks.

Similar to how people make use of gdb configs. I need to think about sharing all the volshell snippets I use.

@ikelos
Copy link
Member

ikelos commented Dec 16, 2024

kvo = self.config['kernel.offset']

Also, we need to be slightly careful when putting in code that accessesd sub-config values using kernel.offset. The . is reasonable but technically I believe it's technically CONFIG_SEPARATOR which means it can change and shouldn't really be coded in as part of a string. I think using interfaces.configuration.path_join('kernel', 'offset') would be ok...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants